#!/usr/bin/env python3
"""Docstring for the config_manager.py module.

@author Juntong
@date   2022-10-02

配置管理器。注意此模块不保证线程安全！

"""
import os
import re
import time
import yaml

CACHE = {
    "MAIN_CONFIG_CACHE": {},
    "MAIN_CONFIG_CACHED_TIME": -1,
}


def path_formate(path: str, *, formate_mapping=None) -> str:
    """
    格式化路径写法, 目的是跨平台. 如去掉首末的空字符，将"~"替换为家目录，将反斜杠替换为斜杠
    Parameters
    ----------
    path: str
        Path to be formatted.
    formate_mapping: None | dict
        更新的

    Returns
    -------
    path_formatted: str
        被更新后的路径
    """
    # -- 去掉首末空字符 --
    path = path.strip()
    # -- 替换家路径 --
    path = os.path.expanduser(path)
    # -- 替换环境变量 --
    path = os.path.expandvars(path)
    # -- 替换关键字符 --
    if formate_mapping is None:
        formate_mapping = {"PROJECT_PATH": PROJECT_PATH}
    path = path.format_map(formate_mapping)
    # -- 替换分隔符 --
    path = path.replace("\\", "/")
    # -- 返回值 --
    return path


# --- 定义常量 ---
PROJECT_PATH = path_formate(os.path.dirname(__file__), formate_mapping={})
CONFIG_PATH = path_formate(os.path.join(os.path.dirname(__file__), "config.yaml"))


# ---


def path_serialize(path: str, *, formate_mapping=None) -> str:
    """
    序列化路径
    Parameters
    ----------
    path: str
        待序列化的路径
    formate_mapping: None | dict[str, str]
        其键是待被替换的字符, 值是被替换的字符

    Returns
    -------
    str
    """
    # -- 处理项目目录 --
    path = re.sub(rf"^{PROJECT_PATH}", "{PROJECT_PATH}", path)
    # -- 处理家目录 --
    home_path = path_formate("~")
    path = re.sub(rf"^{home_path}", "~", path)
    # -- 处理系统环境变量 --
    for new, old in os.environ.items():
        if (  # 不将值替换为系统环境变量的情况:
                (not isinstance(old, str)) or  # - 被替换的值不是文本对象
                (new.startswith("_")) or  # - 环境变量以"_"开头
                (set(old) & {";", "&"})  # - 被替换的值包含了 "不正常" 的字符
        ):
            continue
        path = path.replace(old, "${%s}" % new)
    # -- 处理自定义替换文本 --
    mapping = {}
    if formate_mapping:
        mapping.update(formate_mapping)
    for new, old in mapping.items():
        path = path.replace(old, new.join("{}"))
    # -- 返回值 --
    return path


def load_main_config() -> dict:
    """
    返回本地主配置文件。

    Returns
    -------
    dict
        The data of the main config file.
    """
    if os.path.getmtime(CONFIG_PATH) > CACHE["MAIN_CONFIG_CACHED_TIME"]:
        with open(CONFIG_PATH, "r", encoding="utf-8") as fp:
            data = yaml.safe_load(fp)
        CACHE["MAIN_CONFIG_CACHED_TIME"] = time.time()
        CACHE["MAIN_CONFIG_CACHE"].clear()
        CACHE["MAIN_CONFIG_CACHE"].update(data)

    return CACHE["MAIN_CONFIG_CACHE"]


def write_main_config(data):
    """
    将数据写入主配置
    Parameters
    ----------
    data : dict
        写入的数据

    Returns
    -------
    None
    """
    with open(CONFIG_PATH, "w", encoding="utf-8") as fp:
        yaml.safe_dump(data, fp)


def update_main_config(update=None, **kwargs) -> dict:
    """
    更新主配置
    Parameters
    ----------
    update: dict | None
    kwargs: dict

    Returns
    -------
    data_updated: dict
        更新后的数据
    """
    data = load_main_config()
    if update:
        data.update(update)
    if kwargs:
        data.update(kwargs)
    write_main_config(data)
    return data


default_path_formate_mapping = {"PROJECT_PATH": PROJECT_PATH}
default_path_serialize_mapping = {"PROJECT_PATH": PROJECT_PATH}


class ConfigManager:

    @staticmethod
    def path_formate(path, *, formate_mapping=None):
        if formate_mapping is None:
            formate_mapping = default_path_formate_mapping
        return path_formate(path, formate_mapping=formate_mapping)

    @staticmethod
    def path_serialize(path, *, formate_mapping=None):
        if formate_mapping is None:
            formate_mapping = default_path_serialize_mapping
        return path_serialize(path, formate_mapping=formate_mapping)

    def yaml_load(self, path):
        path = self.path_formate(path)
        if path in CACHE and os.path.getmtime(path) <= CACHE.get(path + "_CACHED_TIME", -1):
            return CACHE[path]
        with open(path, "r", encoding="utf-8") as fp:
            data = yaml.safe_load(fp)
        CACHE[path] = data
        CACHE[path + "_CACHED_TIME"] = time.time()
        return data

    def yaml_dump(self, data, path):
        path = self.path_formate(path)
        with open(path, "w", encoding="utf-8") as fp:
            yaml.safe_dump(data, fp)
        CACHE[path] = data
        CACHE[path + "_CACHED_TIME"] = time.time()

    def __dir__(self):
        return load_main_config().keys()

    def __getattr__(self, item):
        try:
            return object.__getattribute__(self, item)
        except AttributeError:
            return load_main_config()[item]

    def __setattr__(self, key, value):
        update_main_config(key=value)

    def __getitem__(self, item):
        return getattr(self, item)

    def __setitem__(self, key, value):
        return setattr(self, key, value)
